package com.icloud.wallet.boot.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.icloud.common.core.api.ResultCode;
import com.icloud.common.database.enums.*;
import com.icloud.common.database.mapper.*;
import com.icloud.common.database.model.AppCustomer;
import com.icloud.common.database.model.CustomerWallet;
import com.icloud.common.database.model.TOrders;
import com.icloud.common.database.model.TWalletJournal;
import com.icloud.common.web.constants.CommonConstants;
import com.icloud.common.web.enums.StateEnum;
import com.icloud.common.web.exception.ApiException;
import com.icloud.common.web.exception.Asserts;
import com.icloud.common.web.util.AuthUtil;
import com.icloud.wallet.boot.constant.WalletConstants;
import com.icloud.wallet.boot.dto.RechargeOrderDto;
import com.icloud.wallet.boot.dto.WithdrawChargeDto;
import com.icloud.wallet.boot.dto.WithdrawOrderDto;
import com.icloud.wallet.boot.mapper.*;
import com.icloud.wallet.boot.model.*;
import com.icloud.wallet.boot.service.ITOrderService;
import com.icloud.wallet.boot.vo.RechargeOrderVO;
import com.icloud.wallet.boot.vo.WithdrawChargeVO;
import com.icloud.wallet.boot.vo.WithdrawOrderVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrders> implements ITOrderService {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private AppCustomerMapper appCustomerMapper;
    @Autowired
    private AppCustomerAuthMapper appCustomerAuthMapper;
    @Autowired
    private CustomerWalletMapper customerWalletMapper;
    @Autowired
    private TWalletJournalMapper tWalletJournalMapper;
    @Autowired
    private TCollectionJournalMapper tCollectionJournalMapper;
    @Autowired
    private TRechargeOrderItemMapper tRechargeOrderItemMapper;
    @Autowired
    private TWithdrawOrderItemMapper tWithdrawOrderItemMapper;
    @Autowired
    private TOrderMapper tOrderMapper;
    @Autowired
    private AuthUtil authUtil;

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public RechargeOrderVO orderRecharge(RechargeOrderDto dto) {
        LocalDateTime now = LocalDateTime.now();
        CryptoCurrencyEnum cryptoCurrencyEnum = CryptoCurrencyEnum.byCode(dto.getCryptoCurrency());
        if(cryptoCurrencyEnum==null){
            throw new ApiException(ResultCode.ILLEGAL_CRYPTO_CURRENCY);
        }
        String fromAddr = dto.getFrom();
        if(StringUtils.isBlank(fromAddr)){
            throw new ApiException(ResultCode.REQUIRED_ACCOUNT_ADDRESS);
        }
        String toAddr = dto.getTo();
        if(StringUtils.isBlank(toAddr)){
            throw new ApiException(ResultCode.REQUIRED_ACCOUNT_ADDRESS);
        }
        String contractAddr = dto.getContractAddr();

        LambdaQueryWrapper<CustomerWallet> wallQuer = new LambdaQueryWrapper<>();
        if(CryptoCurrencyEnum.isBEP20(cryptoCurrencyEnum.getCode())) {
            wallQuer.eq(CustomerWallet::getBscAddr, toAddr);
        } else {
            throw new ApiException(ResultCode.ILLEGAL_CRYPTO_CURRENCY);
        }
        CustomerWallet wallet = customerWalletMapper.selectOne(wallQuer);
        if(wallet==null){
            log.warn("收款地址[{}]未注册用户", toAddr);
            throw new ApiException(ResultCode.NOT_EXIST_ADDRESS_CUSTOMER);
        }
        //Map<Long, CustomerWallet> walletMap = walletsList.stream().collect(Collectors.toMap(CustomerWallet::getCustomerId, Function.identity()));
        //List<Long> customerIdList = walletsList.stream().map(CustomerWallet::getCustomerId).collect(Collectors.toList());
        Map<Long, CustomerWallet> walletMap = new HashMap<>();
        walletMap.put(wallet.getCustomerId(), wallet);
        List<Long> customerIdList = List.of(wallet.getCustomerId());

        LambdaQueryWrapper<AppCustomer> custQuery = new LambdaQueryWrapper<>();
        custQuery.in(AppCustomer::getId, customerIdList);
        custQuery.eq(AppCustomer::getState, StateEnum.NORMAL.getCode());
        List<AppCustomer> custList = appCustomerMapper.selectList(custQuery);
        if(CollectionUtils.isEmpty(custList)){
            log.warn("收款地址[{}]用户已封号", toAddr);
            throw new ApiException(ResultCode.NOT_EXIST_ADDRESS_CUSTOMER);
        }
        AppCustomer me = custList.get(0);
        Long customerId = me.getId();
        CustomerWallet myWallet = walletMap.get(customerId);
        if(myWallet==null){
            log.warn("客户[{}]钱包未找到", customerId);
            throw new ApiException(ResultCode.NOT_EXIST_WALLET);
        }

        String nowStr = CommonConstants.DEFAULT_DATETIME_FORMATTER.format(now);
        Long orderTimestamp = dto.getOrderTime();
        LocalDateTime orderDate = now;
        if(orderTimestamp!=null) {
            orderDate = LocalDateTime.ofInstant(Instant.ofEpochSecond(orderTimestamp), ZoneId.systemDefault());
        }

        BigDecimal expand = dto.getAmount();
        if(expand==null){
            throw new ApiException(ResultCode.REQUIRED_TX_AMOUNT);
        }
        BigDecimal amount = expand.divide(new BigDecimal(CommonConstants.ETHER2WEI_STRING), CommonConstants.DEFAULT_SCALE, RoundingMode.HALF_UP);
        TOrders tOrders = TOrders.builder()
                .customerId(customerId)
                .orderType(OrderTypeEnum.RECHARGE.getCode())
                .amount(amount)
                .orderTime(orderDate)
                .paymentTime(now)
                .paymentState(PaymentStateEnum.FINISHED.getCode())
                .state(StateEnum.NORMAL.getCode())
                .createdId(customerId)
                .updatedId(customerId)
                .build();
        tOrderMapper.insert(tOrders);

        TRechargeOrderItems tRechargeOrderItems = TRechargeOrderItems.builder()
                .orderId(tOrders.getId())
                .cryptoCurrency(cryptoCurrencyEnum.getCode())
                .contractAddr(contractAddr)
                .payAddr(fromAddr)
                .beneAddr(toAddr)
                .build();
        tRechargeOrderItemMapper.insert(tRechargeOrderItems);

        BigDecimal beforeBalance = myWallet.getBcpc();
        // 更新钱包余额
        BigDecimal afterBalance = beforeBalance.add(amount);

        TWalletJournal journal = TWalletJournal.builder()
                .customerId(customerId)
                .orderId(tOrders.getId())
                .journalType(JournalTypeEnum.RECHARGE.getCode())
                .beforeBalance(beforeBalance)
                .amount(amount)
                .afterBalance(afterBalance)
                .currency(CurrencyEnum.BCPC.getCode())
                .direction(DirectionEnum.DEBIT.getCode())
                .journalState(JournalStateEnum.FINISHED.getCode())
                .remark("支付充值订单")
                .createdId(customerId)
                .updatedId(customerId)
                .build();

        tWalletJournalMapper.insert(journal);

        CustomerWallet updateWallet = CustomerWallet.builder()
                .id(myWallet.getId())
                .bcpc(afterBalance)
                .build();
        customerWalletMapper.updateById(updateWallet);

        TCollectionJournal collectionJournal = TCollectionJournal.builder()
                .customerId(customerId)
                .orderId(tOrders.getId())
                .cryptoCurrency(cryptoCurrencyEnum.getCode())
                .contractAddr(contractAddr)
                .amount(amount)
                .payAddr(toAddr)
                .beneAddr(WalletConstants.COLLECT_ADDRESS)
                .journalState(JournalStateEnum.PENDING.getCode())
                .remark("归集待处理")
                .build();
        tCollectionJournalMapper.insert(collectionJournal);

        RechargeOrderVO vo = new RechargeOrderVO();
        vo.setOrderNo(String.valueOf(tOrders.getId()));
        vo.setPaymentTime(nowStr);
        return vo;
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public WithdrawOrderVO orderWithdraw(WithdrawOrderDto dto) {
        LocalDateTime now = LocalDateTime.now();
        Long customerId = dto.getCustomerId();
        if(customerId==null){
            log.warn("当前客户不存在");
            throw new ApiException(ResultCode.UNAUTHORIZED);
        }
        String cryptoCurrency = dto.getCryptoCurrency();
        CryptoCurrencyEnum cryptoCurrencyEnum = CryptoCurrencyEnum.byCode(cryptoCurrency);
        if(cryptoCurrencyEnum==null){
            log.warn("提现币种错误,不支持[{}]", cryptoCurrency);
            throw new ApiException(ResultCode.ILLEGAL_CRYPTO_CURRENCY);
        }
        AppCustomer me = appCustomerMapper.selectById(customerId);
        if(me==null){
            log.warn("Id为[{}]的客户不存在", customerId);
            throw new ApiException(ResultCode.NOT_EXIST_CUSTOMER);
        }
        if(StateEnum.isStop(me.getState())){
            log.warn("Id为[{}]的客户已被封号", customerId);
            throw new ApiException(ResultCode.STOPPED_CUSTOMER);
        }

        CustomerWallet myWallet = customerWalletMapper.selectOne(new LambdaQueryWrapper<CustomerWallet>().eq(CustomerWallet::getCustomerId, customerId));
        if(myWallet==null) {
            log.warn("客户[{}]钱包未找到", customerId);
            throw new ApiException(ResultCode.NOT_EXIST_WALLET);
        }
        String withdrawAddress = dto.getAddress();
        if(StringUtils.isBlank(withdrawAddress)){
            log.warn("客户[{}]未设置提现地址", customerId);
            throw new ApiException(ResultCode.REQUIRED_ADDRESS);
        }
        if(withdrawAddress.length()!=42 || !(withdrawAddress.startsWith("0x") || withdrawAddress.startsWith("0X"))){
            log.warn("客户[{}]提现地址[{}]不合法", customerId, withdrawAddress);
            throw new ApiException(ResultCode.ADDRESS_FORMAT_ERROR);
        }
        BigDecimal withdarwAmt = dto.getWithdrawAmount();
        BigDecimal inputAmount = dto.getAmount();
        BigDecimal inputCharge = dto.getCharge();
        BigDecimal withdrawable = myWallet.getWithdrawable();
        if(withdrawable.compareTo(withdarwAmt)<0){
            log.warn("客户[{}]提现金额超过最大限制", customerId);
            throw new ApiException(ResultCode.WITHDRAW_AMOUNT_EXCEEDS_MAX);
        }

        WithdrawChargeDto chargeDto = new WithdrawChargeDto();
        BeanUtils.copyProperties(dto, chargeDto);
        WithdrawChargeVO retVO = withdrawCharge(chargeDto);
        BigDecimal amount = retVO.getAmount();
        BigDecimal charge = retVO.getCharge();
        if(inputAmount.compareTo(amount)!=0){
            log.warn("客户[{}]实际到账金额与计算结果不一致", customerId);
            throw new ApiException(ResultCode.TX_AMOUNT_ERROR);
        }
        if(inputCharge.compareTo(charge)!=0){
            log.warn("客户[{}]手续费与计算结果不一致", customerId);
            throw new ApiException(ResultCode.TX_AMOUNT_ERROR);
        }
        BigDecimal beforeBalance = myWallet.getBcpc();
        BigDecimal afterBalance = beforeBalance.subtract(amount).subtract(charge);
        if(afterBalance.compareTo(BigDecimal.ZERO)<0){
            log.warn("客户[{}]的bcpc余额不足,无法提取", customerId);
            throw new ApiException(ResultCode.INSUFFICIENT_BALANCE);
        }
        BigDecimal beforeWithdrawable = myWallet.getWithdrawable();
        BigDecimal afterWithdrawable = beforeWithdrawable.subtract(amount).subtract(charge);
        if(afterWithdrawable.compareTo(BigDecimal.ZERO)<0){
            log.warn("客户[{}]的bcpc余额不足,无法提取", customerId);
            throw new ApiException(ResultCode.INSUFFICIENT_BALANCE);
        }

        Long orderTimestamp = dto.getOrderTime();
        LocalDateTime orderDate = now;
        if(orderTimestamp!=null) {
            orderDate = LocalDateTime.ofInstant(Instant.ofEpochSecond(orderTimestamp), ZoneId.systemDefault());
        }

        TOrders tOrders = TOrders.builder()
                .customerId(customerId)
                .orderType(OrderTypeEnum.WITHDRAW.getCode())
                .amount(withdarwAmt)
                .orderTime(orderDate)
                .paymentState(PaymentStateEnum.UNPAID.getCode())
                .state(StateEnum.NORMAL.getCode())
                .createdId(customerId)
                .updatedId(customerId)
                .build();
        tOrderMapper.insert(tOrders);

        TWithdrawOrderItems tWithdrawOrderItems = TWithdrawOrderItems.builder()
                .orderId(tOrders.getId())
                .cryptoCurrency(cryptoCurrency)
                .amount(amount)
                .charges(charge)
                .withdrawState(HandleStateEnum.UNHANDLED.getCode())
                .beneAddr(withdrawAddress)
                .build();
        tWithdrawOrderItemMapper.insert(tWithdrawOrderItems);

        // 更新钱包余额
        CustomerWallet updateWallet = CustomerWallet.builder()
                .id(myWallet.getId())
                .bcpc(afterBalance)
                .withdrawable(afterWithdrawable)
                .build();
        customerWalletMapper.updateById(updateWallet);

        // 产生待处理流水
        TWalletJournal journal = TWalletJournal.builder()
                .customerId(customerId)
                .orderId(tOrders.getId())
                .journalType(JournalTypeEnum.WITHDRAW.getCode())
                .beforeBalance(beforeBalance)
                .amount(amount)
                .afterBalance(afterBalance)
                .charges(charge)
                .currency(CurrencyEnum.BCPC.getCode())
                .direction(DirectionEnum.CREDIT.getCode())
                .journalState(JournalStateEnum.PENDING.getCode())
                .remark("总余额支付提现订单")
                .createdId(customerId)
                .updatedId(customerId)
                .build();
        tWalletJournalMapper.insert(journal);

        TWalletJournal withdrawJournal = TWalletJournal.builder()
                .customerId(customerId)
                .orderId(tOrders.getId())
                .journalType(JournalTypeEnum.WITHDRAW_ABLE.getCode())
                .beforeBalance(beforeWithdrawable)
                .amount(amount)
                .afterBalance(afterWithdrawable)
                .charges(charge)
                .currency(CurrencyEnum.BCPC.getCode())
                .direction(DirectionEnum.CREDIT.getCode())
                .journalState(JournalStateEnum.PENDING.getCode())
                .remark("可提现余额支付提现订单")
                .createdId(customerId)
                .updatedId(customerId)
                .build();
        tWalletJournalMapper.insert(withdrawJournal);

        WithdrawOrderVO vo = new WithdrawOrderVO();
        BeanUtils.copyProperties(dto, vo);
        vo.setOrderNo(String.valueOf(tOrders.getId()));
        return vo;
    }

    @Override
    public WithdrawChargeVO withdrawCharge(WithdrawChargeDto dto) {
        String cryptoCurrency = dto.getCryptoCurrency();
        CryptoCurrencyEnum cryptoCurrencyEnum = CryptoCurrencyEnum.byCode(cryptoCurrency);
        if(cryptoCurrencyEnum==null){
            log.warn("提现币种错误, 不支持[{}]", cryptoCurrency);
            throw new ApiException(ResultCode.ILLEGAL_CRYPTO_CURRENCY);
        }
        Long customerId = dto.getCustomerId();
        if(customerId==null){
            log.warn("当前客户不存在");
            throw new ApiException(ResultCode.UNAUTHORIZED);
        }
        BigDecimal withdrawAmount = dto.getWithdrawAmount();
        if(withdrawAmount==null || withdrawAmount.compareTo(BigDecimal.ZERO)<=0){
            log.warn("客户[{}]提现币种金额应大于0", customerId);
            throw new ApiException(ResultCode.TX_AMOUNT_ERROR);
        }
        BigDecimal withdrawChargeRate = WalletConstants.WITHDRAW_CHARGE_RATE;
        BigDecimal charge = withdrawAmount.multiply(withdrawChargeRate).setScale(CommonConstants.DEFAULT_SCALE, RoundingMode.HALF_UP);
        BigDecimal amt = withdrawAmount.subtract(charge);

        CustomerWallet customerWallet = customerWalletMapper.selectOne(new LambdaQueryWrapper<CustomerWallet>().eq(CustomerWallet::getCustomerId, customerId));
        Asserts.fail(null == customerWallet,ResultCode.NOT_EXIST_WALLET);

        WithdrawChargeVO vo = new WithdrawChargeVO();
        BeanUtils.copyProperties(dto, vo);
        vo.setWithdrawable(customerWallet.getWithdrawable());
        vo.setAmount(amt);
        vo.setCharge(charge);
        return vo;
    }
}
